package borderPatrol.Calculators;

import java.util.ArrayList;
import java.util.LinkedList;

import borderPatrol.Data.Move;
import borderPatrol.misc.CFRInformationSet;
import borderPatrol.misc.History;
import borderPatrol.misc.Judge;

public class BPValueCalculator extends ValueCalculator {

	public BPValueCalculator() {
		super();
	}

	@Override
	public void updateValues(int iteration) {
		updateValues("1", 1);
		updateValues("2", 2);
	}

	private float[] updateValues(String id, int sign) {

		CFRInformationSet set = gameTree.get(id);
		LinkedList<float[]> valuesFromLowerLevel = new LinkedList<float[]>();

		if (set.getIsTerminal() != 0) {
			return getTerminalValuesToPropagateUp(sign, set);
		}

		for (Move move : set.getMoves()) {
			valuesFromLowerLevel.add(updateValues(getIdWithMove(id, move), sign));
		}

		ArrayList<float[]> strategies = getStrategies(id, set.getPossibleHistories());
		float[] probabilities = getProbabilities(id, set.getPossibleHistories());

		updateSetValues(id, set, valuesFromLowerLevel, strategies, sign, probabilities);

		return getValuesToPropagateUp(id, valuesFromLowerLevel, set.getPossibleHistories(), strategies, sign, set);
	}

	private float[] getTerminalValuesToPropagateUp(int sign, CFRInformationSet set) {

		float[] valuesToPropagateUp = getArrayForSizeAndValue(set.getHistorySize(), set.getIsTerminal() == sign ? 1 : -1);

		if (sign == 2) {
			for (Integer index : set.getPoachersWinningCoordinates()) {
				valuesToPropagateUp[index] = -1;
			}
		}

		return valuesToPropagateUp;
	}

	private float[] getArrayForSizeAndValue(int size, float value) {

		float[] array = new float[size];

		for (int i = 0; i < array.length; i++) {
			array[i] = value;
		}

		return array;
	}

	private float[] getValuesToPropagateUp(String id, LinkedList<float[]> valuesFromLowerLevel, LinkedList<History> possibleHistories, ArrayList<float[]> strategies, int sign, CFRInformationSet set) {

		LinkedList<float[]> valuesWithTerminals = new LinkedList<float[]>();

		for (float[] values : valuesFromLowerLevel) {
			valuesWithTerminals.add(getValuesForCurrentHistories(id, values, possibleHistories, strategies, sign));
		}

		return getMeansOverHistories(valuesWithTerminals, set);
	}

	private float[] getMeansOverHistories(LinkedList<float[]> valuesWithTerminals, CFRInformationSet set) {

		float[] means = new float[valuesWithTerminals.get(0).length];
		int counter = 0;
		
		for (float[] values : valuesWithTerminals) {
			for (int i = 0; i < values.length; i++) {
				means[i] += values[i]*set.getStrategy()[counter];
			}
			counter++;
		}


		return means;
	}

	private ArrayList<float[]> getStrategies(String id, LinkedList<History> possibleHistories) {

		ArrayList<float[]> strategies = new ArrayList<float[]>();

		for (History history : possibleHistories) {
			if (Judge.getInstance().isTerminal(id, history.toString()) == 0) {
				strategies.add(gameTree.get(history.toString()).getStrategy().clone());
			}
		}

		return strategies;
	}

	private float[] getProbabilities(String id, LinkedList<History> possibleHistories) {

		float[] probabilities = new float[getNonTerminalCount(id, possibleHistories)];
		int counter = 0;

		for (History history : possibleHistories) {
			if (Judge.getInstance().isTerminal(id, history.toString()) == 0) {
				probabilities[counter++] = gameTree.get(history.toString()).getProbabilityOfOccurence();
			}
		}

		return getNormalizedArray(probabilities);
	}

	private float[] getNormalizedArray(float[] array) {
		
		float[] normalizedArray = new float[array.length];
		float sum = getSum(array);

		if (sum != 0) {
			for (int i = 0; i < array.length; i++) {
				normalizedArray[i] = array[i]/sum;
			}
		}
		
		return normalizedArray;
	}

	private float getSum(float[] probabilities) {

		float sum = 0;

		for (int i = 0; i < probabilities.length; i++) {
			sum += probabilities[i];
		}

		return sum;
	}

	private float[] getValuesForCurrentHistories(String id, float[] values, LinkedList<History> possibleHistories, ArrayList<float[]> strategies, int sign) {

		float[] valuesToPropagateUp = new float[possibleHistories.size()];
		int counter = 0;
		int offset = 0;
		int strategyOffset = 0;

		for (History history : possibleHistories) {

			if (Judge.getInstance().isTerminal(id, history.toString()) != 0) {
				valuesToPropagateUp[counter++] = Judge.getInstance().isTerminal(id, history.toString()) == sign ? 1 : -1;
				offset++;
				strategyOffset++;
				continue;
			}

			int tempOffset = -1;

			for (int i = counter - offset; i < counter - offset + history.possibleMoves.size(); i++) {
				tempOffset++;
				valuesToPropagateUp[counter] += strategies.get(counter - strategyOffset)[i - counter + offset] * values[i];
			}

			offset -= tempOffset;
			counter++;

		}

		return valuesToPropagateUp;
	}

	private float[] getValuesWeightedByStrategies(String id, float[] values, LinkedList<History> possibleHistories, ArrayList<float[]> strategies, int sign) {

		float[] valuesToPropagateUp = new float[getNonTerminalCount(id, possibleHistories)];
		int counter = 0;
		int offset = 0;

		for (History history : possibleHistories) {

			if (Judge.getInstance().isTerminal(id, history.toString()) != 0) {
				continue;
			}

			int tempOffset = -1;

			for (int i = counter - offset; i < counter - offset + history.possibleMoves.size(); i++) {
				tempOffset++;
				valuesToPropagateUp[counter] += strategies.get(counter)[i - counter + offset] * values[i];
			}

			offset -= tempOffset;
			counter++;

		}

		return valuesToPropagateUp;
	}

	private int getNonTerminalCount(String id, LinkedList<History> possibleHistories) {

		int count = 0;

		for (History history : possibleHistories) {
			if (Judge.getInstance().isTerminal(id, history.toString()) == 0) {
				count++;
			}
		}

		return count;
	}

	private void updateSetValues(String id, CFRInformationSet set, LinkedList<float[]> valuesFromLowerLevel, ArrayList<float[]> strategies, int sign, float[] probabilities) {

		LinkedList<float[]> realValues = new LinkedList<float[]>();

		for (float[] values : valuesFromLowerLevel) {
			realValues.add(getValuesWeightedByStrategies(id, values, set.getPossibleHistories(), strategies, sign));
		}

		// System.out.println(id);
		// System.out.println(realValues.get(0).length+" vs "+probabilities.length);
		// System.out.println(Arrays.toString(probabilities));

		set.setValues(getMeansOverMoves(realValues, probabilities));
	}

	private float[] getMeansOverMoves(LinkedList<float[]> realValues, float[] probabilities) {

		float[] means = new float[realValues.size()];
		int counter = 0;

		for (float[] values : realValues) {

			for (int i = 0; i < values.length; i++) {
				means[counter] += probabilities[i] * values[i];
			}

			counter++;
		}

		return means;
	}

	private String getIdWithMove(String id, Move move) {

		StringBuilder builder = new StringBuilder();

		builder.append(id);
		builder.append(".");
		builder.append(move);

		return builder.toString();
	}
}
